OneToManyWithMappedAssociationEngine.java
package org.codefilarete.stalactite.engine.runtime.onetomany;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.codefilarete.reflection.Accessor;
import org.codefilarete.reflection.AccessorDefinition;
import org.codefilarete.reflection.Mutator;
import org.codefilarete.stalactite.dsl.idpolicy.GeneratedKeysPolicy;
import org.codefilarete.stalactite.engine.EntityPersister;
import org.codefilarete.stalactite.engine.cascade.AfterInsertCollectionCascader;
import org.codefilarete.stalactite.engine.cascade.BeforeDeleteByIdCollectionCascader;
import org.codefilarete.stalactite.engine.cascade.BeforeDeleteCollectionCascader;
import org.codefilarete.stalactite.engine.configurer.onetomany.FirstPhaseCycleLoadListener;
import org.codefilarete.stalactite.engine.listener.DeleteByIdListener;
import org.codefilarete.stalactite.engine.listener.DeleteListener;
import org.codefilarete.stalactite.engine.listener.SelectListener;
import org.codefilarete.stalactite.engine.listener.UpdateListener;
import org.codefilarete.stalactite.engine.runtime.CollectionUpdater;
import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
import org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.JoinType;
import org.codefilarete.stalactite.mapping.Mapping.ShadowColumnValueProvider;
import org.codefilarete.stalactite.mapping.id.assembly.IdentifierAssembler;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.Key;
import org.codefilarete.stalactite.sql.ddl.structure.Key.KeyBuilder;
import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.tool.Duo;
import org.codefilarete.tool.Nullable;
import org.codefilarete.tool.collection.IdentityMap;
import org.codefilarete.tool.collection.Iterables;
import static org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.ROOT_JOIN_NAME;
import static org.codefilarete.tool.Nullable.nullable;
import static org.codefilarete.tool.collection.Iterables.stream;
/**
* @author Guillaume Mary
*/
public class OneToManyWithMappedAssociationEngine<SRC, TRGT, SRCID, TRGTID, S extends Collection<TRGT>, SRCTABLE extends Table<SRCTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>>
extends AbstractOneToManyEngine<SRC, TRGT, SRCID, TRGTID, S>
{
/**
* Foreign key column value store, for insert, update and delete cases : stores parent entity value per child entity,
* (parent can be a null if child was removed from relation).
* Implemented as a ThreadLocal because we can hardly cross layers and methods to pass such a value.
* Cleaned after update and delete.
*/
protected final ThreadLocal<TargetToSourceRelationStorage> currentTargetToSourceRelationStorage = new ThreadLocal<>();
/**
* Storage of relation between TRGT and SRC entities to avoid to depend on "mapped by" properties which is optional.
* Foreign key maintenance code will refer to it.
* @author Guillaume Mary
*/
protected class TargetToSourceRelationStorage {
private final IdentityMap<TRGT, SRC> store = new IdentityMap<>();
TargetToSourceRelationStorage() {
}
private void add(TRGT target, SRC source) {
store.put(target, source);
}
protected SRC get(TRGT target) {
return store.get(target);
}
private SRCID giveSourceId(TRGT trgt) {
return nullable(get(trgt)).map(sourcePersister.getMapping()::getId).get();
}
}
protected final ShadowColumnValueProvider<TRGT, RIGHTTABLE> foreignKeyValueProvider;
public OneToManyWithMappedAssociationEngine(ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister,
MappedManyRelationDescriptor<SRC, TRGT, S, SRCID, RIGHTTABLE> manyRelationDescriptor,
ConfiguredRelationalPersister<SRC, SRCID> sourcePersister,
Set<Column<RIGHTTABLE, ?>> mappedReverseColumns,
Function<SRCID, Map<Column<RIGHTTABLE, ?>, ?>> reverseColumnsValueProvider) {
super(sourcePersister, targetPersister, manyRelationDescriptor);
this.foreignKeyValueProvider = new ShadowColumnValueProvider<TRGT, RIGHTTABLE>() {
@Override
public Set<Column<RIGHTTABLE, ?>> getColumns() {
return mappedReverseColumns;
}
@Override
public Map<Column<RIGHTTABLE, ?>, ?> giveValue(TRGT trgt) {
Map<Column<RIGHTTABLE, ?>, Object> result;
if (giveRelationStorageContext() == null) {
// case of TRGT is also root (SRC) in a cycling parent -> parent relation : when some root entities are
// inserted/updated the insert listener that initializes currentForeignKeyValueProvider on databaseAutoIncrement is not yet called
result = new HashMap<>();
getColumns().forEach(col -> result.put(col, null));
} else {
SRCID srcid = giveRelationStorageContext().giveSourceId(trgt);
result = (Map<Column<RIGHTTABLE, ?>, Object>) reverseColumnsValueProvider.apply(srcid);
}
return result;
}
};
addForeignKeyManager();
}
protected TargetToSourceRelationStorage giveRelationStorageContext() {
return currentTargetToSourceRelationStorage.get();
}
protected void clearRelationStorageContext() {
currentTargetToSourceRelationStorage.remove();
}
protected void addForeignKeyManager() {
targetPersister.<RIGHTTABLE>getMapping().addShadowColumnInsert(foreignKeyValueProvider);
targetPersister.<RIGHTTABLE>getMapping().addShadowColumnUpdate(foreignKeyValueProvider);
}
@Override
public MappedManyRelationDescriptor<SRC, TRGT, S, SRCID, RIGHTTABLE> getManyRelationDescriptor() {
return (MappedManyRelationDescriptor<SRC, TRGT, S, SRCID, RIGHTTABLE>) manyRelationDescriptor;
}
@Override
public String addSelectCascade(boolean loadSeparately) {
// we add target subgraph joins to main persister
String relationJoinNodeName = targetPersister.joinAsMany(ROOT_JOIN_NAME, sourcePersister, manyRelationDescriptor.getCollectionAccessPoint(), sourcePersister.getMainTable().getPrimaryKey(), getManyRelationDescriptor().getReverseColumn(),
manyRelationDescriptor.getRelationFixer(), null, true, loadSeparately);
// we must trigger subgraph event on loading of our own graph, this is mainly for event that initializes things because given ids
// are not those of their entity
SelectListener<TRGT, TRGTID> targetSelectListener = targetPersister.getPersisterListener().getSelectListener();
sourcePersister.addSelectListener(new SelectListener<SRC, SRCID>() {
@Override
public void beforeSelect(Iterable<SRCID> ids) {
// since ids are not those of its entities, we should not pass them as argument, this will only initialize things if needed
targetSelectListener.beforeSelect(Collections.emptyList());
}
@Override
public void afterSelect(Set<? extends SRC> result) {
Set<TRGT> collect = Iterables.stream(result).flatMap(src -> Nullable.nullable(manyRelationDescriptor.getCollectionAccessPoint().get(src))
.map(Collection::stream)
.getOr(Stream.empty()))
.collect(Collectors.toSet());
targetSelectListener.afterSelect(collect);
}
@Override
public void onSelectError(Iterable<SRCID> ids, RuntimeException exception) {
// since ids are not those of its entities, we should not pass them as argument
targetSelectListener.onSelectError(Collections.emptyList(), exception);
}
});
return relationJoinNodeName;
}
@Override
public void addInsertCascade(ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {
sourcePersister.addInsertListener(
new TargetInstancesInsertCascader(targetPersister, manyRelationDescriptor.getCollectionAccessPoint()));
}
@Override
public void addUpdateCascade(ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {
sourcePersister.addUpdateListener(new UpdateListener<SRC>() {
/**
* Implemented to store target-to-source relation, made to help relation maintenance (because foreign key
* maintainer will refer to it) and avoid to depend on "mapped by" properties which is optional
* Made AFTER insert to benefit from id when set by database with IdentifierPolicy is {@link GeneratedKeysPolicy}
*/
@Override
public void afterUpdate(Iterable<? extends Duo<SRC, SRC>> entities, boolean allColumnsStatement) {
storeTargetToSourceRelation(Iterables.mappingIterator(entities, Duo::getLeft), false);
}
/**
* Overridden to help debug
* @return targetToSourceRelationStorer
*/
@Override
public String toString() {
return "targetToSourceRelationStorer";
}
});
addTargetInstancesUpdateCascader(getManyRelationDescriptor().isOrphanRemoval());
sourcePersister.addUpdateListener(new UpdateListener<SRC>() {
@Override
public void afterUpdate(Iterable<? extends Duo<SRC, SRC>> entities, boolean allColumnsStatement) {
clearRelationStorageContext();
}
/**
* Overridden to help debug
* @return targetToSourceRelationStorageCleaner
*/
@Override
public String toString() {
return "targetToSourceRelationStorageCleaner";
}
});
}
protected void addTargetInstancesUpdateCascader(boolean shouldDeleteRemoved) {
Mutator<Duo<SRC, SRC>, Boolean> collectionUpdater = new CollectionUpdater<>(
manyRelationDescriptor.getCollectionAccessPoint(),
targetPersister,
manyRelationDescriptor.getReverseSetter(),
shouldDeleteRemoved);
sourcePersister.addUpdateListener(
new AfterUpdateTrigger<>(collectionUpdater));
}
@Override
public void addDeleteCascade(ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {
sourcePersister.addDeleteListener(new DeleteListener<SRC>() {
@Override
public void beforeDelete(Iterable<? extends SRC> entities) {
storeTargetToSourceRelation(entities, true);
}
});
sourcePersister.addDeleteByIdListener(new DeleteByIdListener<SRC>() {
@Override
public void beforeDeleteById(Iterable<? extends SRC> entities) {
storeTargetToSourceRelation(entities, true);
}
});
if (getManyRelationDescriptor().isOrphanRemoval()) {
// adding deletion of many-side entities
sourcePersister.addDeleteListener(
new DeleteTargetEntitiesBeforeDeleteCascader<>(targetPersister, manyRelationDescriptor.getCollectionAccessPoint()));
// we add the deleteById event since we suppose that if delete is required then there's no reason that rough delete is not
sourcePersister.addDeleteByIdListener(
new DeleteByIdTargetEntitiesBeforeDeleteByIdCascader<>(targetPersister, manyRelationDescriptor.getCollectionAccessPoint()));
} else // entity shouldn't be deleted, so we may have to update it
if (manyRelationDescriptor.getReverseSetter() != null) {
// we cut the link between target and source
// NB : we don't take versioning into account overall because we can't : how to do it since we miss the unmodified version ?
sourcePersister.addDeleteListener(new BeforeDeleteCollectionCascader<SRC, TRGT>(targetPersister) {
@Override
public void beforeDelete(Iterable<? extends SRC> entities) {
List<TRGT> targets = stream(entities).flatMap(c -> getTargets(c).stream()).collect(Collectors.toList());
targets.forEach(e -> manyRelationDescriptor.getReverseSetter().set(e, null));
targetPersister.updateById(targets);
}
@Override
protected Collection<TRGT> getTargets(SRC src) {
return nullable(manyRelationDescriptor.getCollectionAccessPoint().get(src)).getOr(manyRelationDescriptor.getCollectionFactory());
}
});
}
sourcePersister.addDeleteListener(new DeleteListener<SRC>() {
@Override
public void afterDelete(Iterable<? extends SRC> entities) {
clearRelationStorageContext();
}
});
sourcePersister.addDeleteByIdListener(new DeleteByIdListener<SRC>() {
@Override
public void afterDeleteById(Iterable<? extends SRC> entities) {
clearRelationStorageContext();
}
});
}
/**
* Method to be invoked in case of entity cycle detected in its persistence configuration.
* We add a second phase load because cycle can hardly be supported by simply joining things together, in particular due to that
* Query and SQL generation don't support several instances of table and columns in them (aliases generation must be enhanced), and
* overall column reading will be messed up because of that (to avoid all of this we should have mapping strategy clones)
*
* @param firstPhaseCycleLoadListener code to be invoked when reading rows
*/
@Override
public void addSelectCascadeIn2Phases(FirstPhaseCycleLoadListener<SRC, TRGTID> firstPhaseCycleLoadListener) {
// Join is declared on non-added tables : Person (alias = null) / Person (alias = null)
PrimaryKey<SRCTABLE, SRCID> sourcePrimaryKey = sourcePersister.<SRCTABLE>getMainTable().<SRCID>getPrimaryKey();
SRCTABLE relationOwnerTable = sourcePrimaryKey.getTable();
Table relationOwnerTableClone = new Table(relationOwnerTable.getName());
KeyBuilder<?, SRCID> relationOwnerPrimaryKeyBuilder = Key.from(relationOwnerTableClone);
sourcePrimaryKey.getColumns().forEach(column ->
relationOwnerPrimaryKeyBuilder.addColumn(relationOwnerTableClone.addColumn(column.getName(), column.getJavaType()))
);
KeyBuilder<Table, SRCID> relationOwnerClone = Key.from(relationOwnerTableClone);
getManyRelationDescriptor().getReverseColumn().getColumns().forEach(column ->
relationOwnerClone.addColumn(relationOwnerTableClone.addColumn(column.getExpression(), column.getJavaType()))
);
IdentifierAssembler<TRGTID, SRCTABLE> targetIdentifierAssembler = targetPersister.getMapping().getIdMapping().getIdentifierAssembler();
Key<Table, SRCID> rightKey = relationOwnerClone.build();
Key<?, SRCID> rightPrimaryKey = relationOwnerPrimaryKeyBuilder.build();
sourcePersister.getEntityJoinTree().addPassiveJoin(
ROOT_JOIN_NAME,
sourcePrimaryKey,
rightKey,
relationOwnerTableClone.getName() + "_" + AccessorDefinition.giveDefinition(getManyRelationDescriptor().getCollectionAccessPoint()).getName(),
JoinType.OUTER,
rightPrimaryKey.getColumns(),
(src, columnValueProvider) -> firstPhaseCycleLoadListener.onFirstPhaseRowRead(src, targetIdentifierAssembler.assemble(columnValueProvider)),
true);
}
public class TargetInstancesInsertCascader extends AfterInsertCollectionCascader<SRC, TRGT> {
private final Accessor<SRC, ? extends Collection<TRGT>> collectionGetter;
public TargetInstancesInsertCascader(EntityPersister<TRGT, TRGTID> targetPersister, Accessor<SRC, ? extends Collection<TRGT>> collectionGetter) {
super(targetPersister);
this.collectionGetter = collectionGetter;
}
@Override
public void afterInsert(Iterable<? extends SRC> entities) {
storeTargetToSourceRelation(entities, false);
super.afterInsert(entities);
clearRelationStorageContext();
}
@Override
protected void postTargetInsert(Iterable<? extends TRGT> entities) {
// Nothing to do. Identified#isPersisted flag should be fixed by target persister
}
@Override
protected Collection<TRGT> getTargets(SRC source) {
return collectionGetter.get(source);
}
}
/**
* Triggers given consumer after update
*
* @param <I>
*/
public static class AfterUpdateTrigger<I> implements UpdateListener<I> {
private final Mutator<Duo<? extends I, ? extends I>, Boolean> afterUpdateListener;
public AfterUpdateTrigger(Mutator<? extends Duo<? extends I, ? extends I>, Boolean> afterUpdateListener) {
this.afterUpdateListener = (Mutator<Duo<? extends I, ? extends I>, Boolean>) afterUpdateListener;
}
@Override
public void afterUpdate(Iterable<? extends Duo<I, I>> entities, boolean allColumnsStatement) {
entities.forEach(entry -> afterUpdateListener.set(entry, allColumnsStatement));
}
}
public static class DeleteTargetEntitiesBeforeDeleteCascader<I, O> extends BeforeDeleteCollectionCascader<I, O> {
private final Accessor<I, ? extends Collection<O>> collectionGetter;
public DeleteTargetEntitiesBeforeDeleteCascader(EntityPersister<O, ?> targetPersister, Accessor<I, ? extends Collection<O>> collectionGetter) {
super(targetPersister);
this.collectionGetter = collectionGetter;
}
@Override
protected Collection<O> getTargets(I i) {
return collectionGetter.get(i);
}
}
public static class DeleteByIdTargetEntitiesBeforeDeleteByIdCascader<I, O> extends BeforeDeleteByIdCollectionCascader<I, O> {
private final Accessor<I, ? extends Collection<O>> collectionGetter;
public DeleteByIdTargetEntitiesBeforeDeleteByIdCascader(EntityPersister<O, ?> targetPersister,
Accessor<I, ? extends Collection<O>> collectionGetter) {
super(targetPersister);
this.collectionGetter = collectionGetter;
}
@Override
protected void postTargetDelete(Iterable<O> entities) {
// no post treatment to do
}
@Override
protected Collection<O> getTargets(I i) {
return collectionGetter.get(i);
}
}
/**
* Store target-to-source relation in current thread storage
*/
private void storeTargetToSourceRelation(Iterable<? extends SRC> sourceEntities, boolean relationIsNullified) {
if (giveRelationStorageContext() == null) {
currentTargetToSourceRelationStorage.set(new TargetToSourceRelationStorage());
}
for (SRC sourceEntity : sourceEntities) {
S collection = manyRelationDescriptor.getCollectionAccessPoint().get(sourceEntity);
nullable(collection).getOr(manyRelationDescriptor.getCollectionFactory()).forEach(trgt -> {
giveRelationStorageContext().add(trgt, relationIsNullified ? null : sourceEntity);
});
}
}
}